/*
 * Copyright (c) 2018-2023 北京华恒盛世科技有限公司
 * QUICKPOOL is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *     http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
*/

#include "xdrbase.h"
#include "channel.h"
#include "api.h"

static int calljs_modify_job(struct request *, struct QuickPoolSubmitReply *, struct poolMaster *);

/**
 * @brief       API: modify a job submit parameter in the cluster.
 *
 * @param[in]   req          #1: modify job request key values
 * @param[out]  submitreply  #2: modify job reply from JS
 * @param[in]   jobid        #3: job ID being modified
 * @param[in]   master       #4: master(port and name) to be called
 *
 * @retval      0            #1: succeed
 * @retval      -1           #2: failed
 *
 * @note        caller should calloc memory for QuickPoolSubmitReply if need.
 *              For JS replied error, caller should handle it.
 *              We call JS to get the job information first, if everything is 
 *              ok, we call JS really modify the job submit parameter.
 */
int
client_modify_job(struct request *req, struct QuickPoolSubmitReply *submitreply,
                  JOBID_T jobid, struct poolMaster *master)
{
    if (jobid <= 0) { // 0 is not a valid job id.
        log_message(ERR, NOCLASS, "%s: job id is not specified.", __func__);
        return -1;
    }

    unsigned int i;
    enum submitType submit=CMD_MODIFY_JOB;
    unsigned int options=0, options2=0, optIndex=0, opt2Index=0;
    time_t beginTime=0, termTime=0;
    char *chkpntDir=NULL, *chkpntMethod=NULL;
    for (i=0; i<req->number; i++) {
        switch (req->keyvalues[i].key) {
        case REQUEST_KEY_SUBMIT_RESREQ:
        case REQUEST_KEY_SUBMIT_PRECMD:
        case REQUEST_KEY_SUBMIT_DEPENDENCY:
        case REQUEST_KEY_SUBMIT_JOBNAME:
        case REQUEST_KEY_SUBMIT_ERRFILE:
        case REQUEST_KEY_SUBMIT_INFILE:
        case REQUEST_KEY_SUBMIT_OUTFILE:
        case REQUEST_KEY_SUBMIT_XFILE:
        case REQUEST_KEY_SUBMIT_NODE:
            strip_newline(req->keyvalues[i].value);
            break;
        case REQUEST_KEY_SUBMIT_BEGINTIME:
            beginTime = atoi(req->keyvalues[i].value);
            break;
        case REQUEST_KEY_SUBMIT_TERMTIME:
            termTime = atoi(req->keyvalues[i].value);
            break;
        case REQUEST_KEY_SUBMIT_OPTIONS:
            options = atoi(req->keyvalues[i].value);
            optIndex = i;
            break;
        case REQUEST_KEY_SUBMIT_OPTIONS2:
            options2 = atoi(req->keyvalues[i].value);
            opt2Index = i;
            break;
        case REQUEST_KEY_CHKPNT_DIR:
            strip_newline(req->keyvalues[i].value);
            chkpntDir = req->keyvalues[i].value;
            break;
        case REQUEST_KEY_CHKPNT_METHOD:
            strip_newline(req->keyvalues[i].value);
            chkpntMethod = req->keyvalues[i].value;
            break;
        }
    }

    unlink_delta_files();

    struct lenData ed;
    memset(&ed, 0, sizeof(struct lenData));

    unsigned int n;
    if (!(options & SUBOPT_NOFORK)) { // do not fork if required so.
        if (setenv_for_submitext(&ed, req) < 0) {
            return -1;
        }
    }

    if (ed.len > 0) {
	FREEUP(ed.data);
    }

    change_submit_request(req, submit);

    n = req->number;
    options |= SUBOPT_UNIT_KB;

    if (beginTime > 0 && termTime > 0 && beginTime > termTime) {
        log_message(ERR, NOCLASS, "%s: job terminate time should be later than job begin time.",
                    __func__);
        return -1;
    }

    if (NULL != chkpntDir && NULL == chkpntMethod) {

        // not specify chkpnt metohd, use CHKPNT_METHOD parameter or default.
        if (baseParams[QP_CHKPNT_METHOD].string_value != NULL) {
            chkpntMethod = baseParams[QP_CHKPNT_METHOD].string_value; 
        } else {
            chkpntMethod = DEFAULT_STRING;
        }

        req->keyvalues[n].key = REQUEST_KEY_CHKPNT_METHOD;
        req->keyvalues[n].value = copy_string(chkpntMethod);
        n++;
    }

    req->keyvalues[n].key = REQUEST_KEY_SUBMIT_TIME;
    req->keyvalues[n].value = (char *)calloc(12, sizeof(char));
    if (NULL == req->keyvalues[n].value) {
        quickpool_errno = ERROR_MEMORY;
        log_message(ERR, NOCLASS, STR_FUNC_D_FAIL_M, __func__, "calloc", 12);
        return -1;
    }
    snprintf(req->keyvalues[n].value, 12, "%ld", time(NULL));
    n++;

    if (optIndex > 0) {
        snprintf(req->keyvalues[optIndex].value, 12, "%d", options);
    } else {
        req->keyvalues[n].key = REQUEST_KEY_SUBMIT_OPTIONS;
        req->keyvalues[n].value = (char *)calloc(12, sizeof(char));
        if (NULL == req->keyvalues[n].value) {
            quickpool_errno = ERROR_MEMORY;
            log_message(ERR, NOCLASS, STR_FUNC_D_FAIL_M, __func__, "calloc", 12);
            return -1;
        }
        snprintf(req->keyvalues[n].value, 12, "%d", options);
        n++;
    }

    if (opt2Index > 0) {
        snprintf(req->keyvalues[opt2Index].value, 12, "%d", options2);
    } else {
        req->keyvalues[n].key = REQUEST_KEY_SUBMIT_OPTIONS2;
        req->keyvalues[n].value = (char *)calloc(12, sizeof(char));
        if (NULL == req->keyvalues[n].value) {
            quickpool_errno = ERROR_MEMORY;
            log_message(ERR, NOCLASS, STR_FUNC_D_FAIL_M, __func__, "calloc", 12);
            return -1;
        }
        snprintf(req->keyvalues[n].value, 12, "%d", options2);
        n++;
    }

    req->number = n;
    if (set_more_request(req, submit) < 0) {
        return -1;
    }

    struct request jobRequest;
    memset(&jobRequest, 0, sizeof(struct request));

    jobRequest.number = 1;
    jobRequest.keyvalues = (struct keyvalue *)calloc(jobRequest.number,
                                                     sizeof(struct keyvalue));
    if (NULL == jobRequest.keyvalues) {
        quickpool_errno = ERROR_MEMORY;
        log_message(ERR, NOCLASS, STR_FUNC_D_FAIL_M, __func__, "calloc",
                    jobRequest.number*sizeof(struct keyvalue));
        return -1;
    }

    jobRequest.keyvalues[0].key = REQUEST_KEY_JOBIDS;
    jobRequest.keyvalues[0].value = (char *)calloc(32, sizeof(char));
    if (NULL == jobRequest.keyvalues[0].value) {
        quickpool_errno = ERROR_MEMORY;
        log_message(ERR, NOCLASS, STR_FUNC_D_FAIL_M, __func__, "calloc", 32);
        return -1;
    }
    snprintf(jobRequest.keyvalues[0].value, 32, "%lld", jobid);

    // first we call JS to get the job information of which is being modified.
    struct QuickPoolJobReply *jobReply;
    quickpool_errno = 0;
    jobReply = client_get_jobids(&jobRequest, master);
    if (NULL == jobReply) {

        gf_xdr_free(gf_xdr_request, &jobRequest);
        fprintf(stderr, "Faild to get job <%lld> due to above reason.\n", jobid);

        return -1;
    }

    if (0 < jobReply->num_error) {

        for (i = 0; i < jobReply->num_error; i++) {
            fprintf(stderr, "%s Job not modified.\n", jobReply->error_messages[i]);
        }

        gf_xdr_free(gf_xdr_request, &jobRequest);
        gf_xdr_free(libxdr_job_reply, jobReply);
        FREEUP(jobReply);

        return -1;
    }

    for (i = 0; i < jobReply->num_job; i++) {

        struct QuickPoolJobInfo *jobInfo;
        jobInfo = client_get_jobinfo();
        if (NULL == jobInfo) {
            continue;
        }

        // check if we can modify the job or not.
        if ((termTime != 0 && termTime != DELETE_NUMBER) && beginTime == 0 
            && jobInfo->submit->begin_time > termTime) {

            char timebuf[TIMESTRINGLEN+1];
            log_message(ERR, NOCLASS, "Job terminte time[%s] is ealier than job begin time[%s]",
                        ctime_2string(CTIME_FORMAT_a_b_d_T_Y, &termTime, timebuf),
                        ctime_2string(CTIME_FORMAT_a_b_d_T_Y, &jobInfo->submit->begin_time, timebuf));

            gf_xdr_free(gf_xdr_request, &jobRequest);
            gf_xdr_free(libxdr_job_reply, jobReply);
            FREEUP(jobReply);
            gf_xdr_free(libxdr_jobinfo, jobInfo);
            FREEUP(jobInfo);
            client_finish_getting();

            return -1;
        }

        if (beginTime != 0 && termTime == 0 && jobInfo->submit->terminate_time > 0
            && jobInfo->submit->terminate_time < beginTime) {

            char timebuf[TIMESTRINGLEN];
            log_message(ERR, NOCLASS, "Job terminte time[%s] is ealier than job begin time[%s]",
                        ctime_2string(CTIME_FORMAT_a_b_d_T_Y, &jobInfo->submit->terminate_time, timebuf),
                        ctime_2string(CTIME_FORMAT_a_b_d_T_Y, &beginTime, timebuf));

            gf_xdr_free(gf_xdr_request, &jobRequest);
            gf_xdr_free(libxdr_job_reply, jobReply);
            FREEUP(jobReply);
            gf_xdr_free(libxdr_jobinfo, jobInfo);
            FREEUP(jobInfo);
            client_finish_getting();

            return -1;
        }

        gf_xdr_free(libxdr_jobinfo, jobInfo);
        FREEUP(jobInfo);
    }

    gf_xdr_free(gf_xdr_request, &jobRequest);
    if (NULL != jobReply) {
        gf_xdr_free(libxdr_job_reply, jobReply);
        FREEUP(jobReply);
    }
    client_finish_getting();

    // do modify
    int ret;
    ret = calljs_modify_job(req, submitreply, master);

    return (ret);
}  // end function client_modify_job

/* ------------------------------------------------
 *
 *  below are static functions used in this file
 *
 * ------------------------------------------------ */

/**
 * @brief       call js to really modify the job submit parameter.
 *
 * @param[in]   req          #1: modify job request key values
 * @param[out]  submitreply  #2: modify job reply from JS
 * @param[in]   master       #4: master(port and name) to be called
 *
 * @retval      0            #1: succeed
 * @retval      -1           #2: failed
 */
static int
calljs_modify_job(struct request *req, struct QuickPoolSubmitReply *submitreply,
                  struct poolMaster *master)
{
    struct auth auth;
    gf_set_auth(&auth, commandParams[AUTH_KIND].string_value);

    unsigned int size; // for calculating the size of the request.
    size = gf_xdrsize_request(req);
    size += gf_xdrsize_auth(&auth);
    size += HEADER_LEN;

    char *request;
    request = (char *)calloc(size, sizeof(char));
    if (NULL == request) {

        log_message(ERR, NOCLASS, STR_FUNC_D_FAIL_M, __func__, "calloc", size);
        gf_xdr_free(gf_xdr_auth, (void *)&auth);
        quickpool_errno = ERROR_MEMORY;
        return -1;
    }

    struct header tohdr, outhdr;
    gf_init_header(&tohdr);
    tohdr.reqcode = BATCH_JOB_MODIFY;

    XDR xdrs;
    xdrmem_create(&xdrs, request, size, XDR_ENCODE);

    if (!gf_xdr_message(&xdrs, req, &tohdr, &auth, gf_xdr_request, NULL)) {

        // fail to encode request.
        log_message(ERR, NOCLASS, STR_FUNC_FAIL, __func__, "gf_xdr_message/gf_xdr_request");
        xdr_destroy(&xdrs);
        FREEUP(request);
        gf_xdr_free(gf_xdr_auth, (void *)&auth);

        return -1;
    }

    char *masterList;
    unsigned int port;
    unsigned int connTimeout;
    unsigned int recvTimeout;    
    if (NULL != master) { // client set master, let's call it directly.
        masterList = master->master;
        port = master->port;
        connTimeout = master->conntimeout;
        recvTimeout = master->recvtimeout;
    } else {
        masterList = qp_getmastername();
        if (NULL == masterList) {
            // JM do not know who is master, let's try g_masterlist.
            masterList = g_masterlist;
        }

        port = g_js_port;
        connTimeout = g_conntimeout;
        recvTimeout = g_recvtimeout;
    }
    
    char *reply;
    int cc, fd;
    cc = gf_callmaster(masterList, port, connTimeout, recvTimeout, g_connretries,
                       XDR_GETPOS(&xdrs), request, &outhdr, &reply, &fd);

    if (0 < fd) {
        gf_chan_close(fd);
    }
    xdr_destroy(&xdrs);
    FREEUP(request);
    gf_xdr_free(gf_xdr_auth, (void *)&auth);

    if (0 > cc) {

        FREEUP(reply);
        log_message(ERR, NOCLASS, STR_FUNC_S_FAIL, __func__, "gf_callmaster", masterList);
        return -1;
    }

    // got job modify reply from JS, decode it.
    xdrmem_create(&xdrs, reply, outhdr.length, XDR_DECODE);
    if (!libxdr_submit_reply(&xdrs, submitreply, &outhdr, NULL)) {

        log_message(ERR, NOCLASS, STR_FUNC_FAIL, __func__, "libxdr_submit_reply");
        xdr_destroy(&xdrs);
        FREEUP(reply);

        return -1;
    }

    xdr_destroy(&xdrs);
    FREEUP(reply);

    quickpool_errno = outhdr.reqcode;
    if (quickpool_errno == 0) {
        return (0);
    }

    return -1;
} // end function calljs_modify_job
